#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''parsexml.py: Parse Tumblr XML file and produce Markdown text file
This program is a part of Tumblr Tools
Copyright (c) Dmitri Brechalov, 2009

Usage: [python] parsexml.py xml-file text-file

'''

import sys, re
from sgmllib import SGMLParser

import markdown2

u = lambda s: s.decode('utf-8')
s = lambda s: s.encode('utf-8')

tag_map = {
    'regular-title':      'title',
    'regular-body':       'body',
    'link-text':          'name',
    'link-url':           'url',
    'link-description':   'description',
    'quote-text':         'quote',
    'quote-source':       'source',
    'photo-caption':      'caption',
    'photo-url':          'url',
    'conversation-title': 'title',
    'conversation-text':  'conversation',
    'video-caption':      'caption',
    'video-source':       'SOURCE',
    'video-player':       'PLAYER',
    'audio-caption':      'caption',
    'audio-player':       'PLAYER',
    }

POST_TYPES = {
    'regular':      ('title', 'date', 'time', 'body'),
    'photo':        ('url', 'caption'),
    'link':         ('name', 'url', 'description'),
    'quote':        ('quote', 'source', 'caption'),
    'conversation': ('title', 'conversation'),
    'video':        ('PLAYER', 'caption'),
    'audio':        ('PLAYER', 'caption')
    }

class AttrList(object):
    def __init__(self, data=None):
        if data is None:
            data = dict()
        self.data = data.copy()
    def set(self, key, val):
        self.data[key] = val
    def get(self, key, default):
        if not self.data.has_key(key):
            return default
        else:
            return self.data[key]

class TumblrParser(SGMLParser):
    RAW_FIELDS = ('regular-body', 'quote-source', 'quote-text', 'link-description', 'photo-caption', 'conversation-text')
    def __init__(self, callback):
        SGMLParser.__init__(self)
        self.post = None
        self.curtag = None
        self.callback = callback
        self.raw_data = False

    def __list2dict(self, lst):
        result = AttrList()
        for k, v in lst:
            result.set(k, v)
        return result

    def start_post(self, attrs):
        if self.raw_data:
            sattrs = ' '.join(['%s="%s"' % (k, v) for k, v in attrs])
            if sattrs: sattrs = ' ' + sattrs
            data = '<post%s>' % (sattrs)
            self.handle_data(data)
            return
        attrs = self.__list2dict(attrs)
        self.attrs = attrs
        self.post = dict()
        for k in POST_TYPES[attrs.get('type', 'regular')]:
            self.post[k] = ''
        self.post['type'] = attrs.get('type', 'regular')
        dt = attrs.get('date-gmt', '')
        self.post['date'] = dt[:10]
        self.post['time'] = dt[11:16]
        self.post['timestamp'] = attrs.get('unix-timestamp', '')
        self.post['format'] = attrs.get('format', '')
        self.post['tags'] = list()
        private = attrs.get('private', '')
        if private == 'true':
            self.post['private'] = 1

    def unknown_starttag(self, name, attrs):
        if self.raw_data:
            sattrs = ' '.join(['%s="%s"' % (k, v) for k, v in attrs])
            if sattrs: sattrs = ' ' + sattrs
            data = '<%s%s>' % (name, sattrs)
            self.handle_data(data)
            return
        attrs = self.__list2dict(attrs)
        self.attrs = attrs
        if self.post is not None:
            if name in self.RAW_FIELDS:
                self.raw_data = True
            self.curtag = name

    def end_post(self):
        if self.raw_data:
            self.handle_data('</post>')
            return
#         print self.post
#         print '----'
        self.post['tags'] = ', '.join(self.post['tags'])
        self.callback(self.post)
        self.post = None
        self.curtag = None

    def unknown_endtag(self, name):
        if name in self.RAW_FIELDS:
            self.raw_data = False
        elif self.raw_data:
            self.handle_data('</%s>' % (name))

    def handle_data(self, content):
#         content = content
        if self.curtag == 'tag':
            self.post['tags'].append(content)
        elif self.curtag == 'photo-url':
            width = self.attrs.get('max-width', '')
            self.post['-'.join(('url', width))] = content
        elif tag_map.has_key(self.curtag):
            try:
                self.post[tag_map[self.curtag]] += content
            except:
                self.post[tag_map[self.curtag]] = content


def printPost(post):
    for k, v in post.items():
        print k, ':', v
    print '----'

class MarkdownPrinter(object):
    TEMPLATES = {
        'regular': '''
### %(title)s ###

%(body)s

_(@%(time)s tags: %(tags)s)_
''',
        'photo': '''
![](%(url-500)s)

%(caption)s

_(@%(time)s tags: %(tags)s)_
''',
        'quote': '''
> &laquo;%(quote)s&raquo;

_(@%(time)s tags: %(tags)s)_
''',
        'link': '''### %(name)s ###

<%(url)s>

_(@%(time)s tags: %(tags)s)_
''',
        'conversation': '''### %(title)s ###

%(conversation)s

_(@%(time)s tags: %(tags)s)_
''',
        'video': '''
''',
        'audio': '''
''',
        }
    DATE_TEMPLATE = '\n\n## %s ##\n\n'
    HEADER = ''
    FOOTER = ''
    
    def __init__(self, fd=sys.stdout):
        self.cur_date = None
        self.fd = fd

    def markdown(self, post):
        for k in ('body', 'description', 'caption', 'quote', 'source', 'conversation'):
            if post.has_key(k) and post[k]:
                post[k] = s(markdown2.markdown(post[k])).replace('--', '&mdash;')

    def printDate(self, date):
        self.fd.write(self.DATE_TEMPLATE % date)
        self.cur_date = date

    def fixMarkdown(self, q):
        q = q.strip()
        if q.startswith('<p>'):
            q = q[3:]
        if q.endswith('</p>'):
            q = q[:-4]
        return q

    def printPost(self, post):
        date = post['date']
        if date != self.cur_date:
            self.printDate(date)
#         if post['format'] == 'markdown':
#             self.markdown(post)
        t = self.TEMPLATES[post['type']]
#         if post['type'] == 'quote':
#             post['quote'] = self.fixMarkdown(post['quote'])
#             post['source'] = self.fixMarkdown(post['source'])
        try:
            self.fd.write(t % post)
        except Exception, details:
            print 'ERROR:', details
            print post

    def pprint(self, lst):
        self.fd.write(self.HEADER)
        for post in lst:
            self.printPost(post)
        self.fd.write(self.FOOTER)

def sortByTimestamp(x, y):
    return int(x['timestamp']) - int(y['timestamp'])

if __name__ == '__main__':
    if len(sys.argv) != 3:
        sys.stderr.write(__doc__)
        sys.exit(1)
    xmlfile = sys.argv[1]
    txtfile = sys.argv[2]
    posts = list()
    parser = TumblrParser(posts.append)
    print '**** reading data...'
    try:
        data = open(xmlfile).read()
    except:
        sys.stderr.write('Cannot read file %s\n' % (xmlfile))
        sys.exit(1)
    print '**** parsing...'
    parser.feed(data)
    parser.close()
    print '     parsed', len(posts), 'posts'
    print '**** sorting...'
    posts.sort(sortByTimestamp)
    print '**** printing...'
    try:
        f = open(txtfile, 'w')
    except:
        sys.stderr.write('Cannot open file %s\n' % (txtfile))
        sys.exit(1)
    printer = MarkdownPrinter(f)
    printer.pprint(posts)
    f.close()
    print '**** done.'
